<template>
    <div
        :class="[
            type === 'textarea' ? 'zx-textarea' : 'zx-input',
            inputSize ? 'zx-input--' + inputSize : '',
            {
                'is-disabled': inputDisabled,
                'is-exceed': inputExceed,
                'zx-input-group': $slots.prepend || $slots.append,
                'zx-input-group--append': $slots.append,
                'zx-input-group--prepend': $slots.prepend,
                'zx-input--prefix': $slots.prefix || prefixIcon,
                'zx-input--suffix': $slots.suffix || suffixIcon || clearable || showPassword
            }
        ]"
        @mouseenter="hovering = true"
        @mouseleave="hovering = false"
    >
        <template v-if="type !== 'textarea'">
            <!-- 前置元素 -->
            <div v-if="$slots.prepend" class="zx-input-group__prepend">
                <slot name="prepend" />
            </div>
            <input
                v-if="type !== 'textarea'"
                ref="input"
                :tabindex="tabindex"
                class="zx-input__inner"
                v-bind="$attrs"
                :type="showPassword ? (passwordVisible ? 'text': 'password') : type"
                :disabled="inputDisabled"
                :readonly="readonly"
                :autocomplete="autoComplete || autocomplete"
                :aria-label="label"
                @compositionstart="handleCompositionStart"
                @compositionupdate="handleCompositionUpdate"
                @compositionend="handleCompositionEnd"
                @input="handleInput"
                @focus="handleFocus"
                @blur="handleBlur"
                @change="handleChange"
            >
            <!-- 前置内容 -->
            <span v-if="$slots.prefix || prefixIcon" class="zx-input__prefix">
                <slot name="prefix" />
                <i
                    v-if="prefixIcon"
                    class="zx-input__icon"
                    :class="prefixIcon"
                />
            </span>
            <!-- 后置内容 -->
            <span
                v-if="getSuffixVisible()"
                class="zx-input__suffix"
            >
                <span class="zx-input__suffix-inner">
                    <template v-if="!showClear || !showPwdVisible || !isWordLimitVisible">
                        <slot name="suffix" />
                        <i
                            v-if="suffixIcon"
                            class="zx-input__icon"
                            :class="suffixIcon"
                        />
                    </template>
                    <i
                        v-if="showClear"
                        class="zx-input__icon zx-icon-close-circle-fill zx-input__clear"
                        @mousedown.prevent
                        @click="clear"
                    />
                    <i
                        v-if="showPwdVisible"
                        class="zx-input__icon zx-icon-view zx-input__clear"
                        @click="handlePasswordVisible"
                    />
                    <span v-if="isWordLimitVisible" class="zx-input__count">
                        <span class="zx-input__count-inner">
                            {{ textLength }}/{{ upperLimit }}
                        </span>
                    </span>
                </span>
                <i
                    v-if="validateState"
                    class="zx-input__icon"
                    :class="['zx-input__validateIcon', validateIcon]"
                />
            </span>
            <!-- 后置元素 -->
            <div v-if="$slots.append" class="zx-input-group__append">
                <slot name="append" />
            </div>
        </template>
        <textarea
            v-else
            ref="textarea"
            :tabindex="tabindex"
            class="zx-textarea__inner"
            v-bind="$attrs"
            :disabled="inputDisabled"
            :readonly="readonly"
            :autocomplete="autoComplete || autocomplete"
            :style="textareaStyle"
            :aria-label="label"
            @compositionstart="handleCompositionStart"
            @compositionupdate="handleCompositionUpdate"
            @compositionend="handleCompositionEnd"
            @input="handleInput"
            @focus="handleFocus"
            @blur="handleBlur"
            @change="handleChange"
        />
        <span v-if="isWordLimitVisible && type === 'textarea'" class="zx-input__count">{{ textLength }}/{{ upperLimit }}</span>
    </div>
</template>
<script>
import emitter from '@src/mixins/emitter';
import Migrating from '@src/mixins/migrating';
import calcTextareaHeight from './calcTextareaHeight';
import merge from '@src/utils/merge';
import { isKorean } from '@src/utils/shared';


export default {
    name: 'ZxInput',

    componentName: 'ZxInput',

    mixins: [emitter, Migrating],

    inheritAttrs: false,

    inject: {
        zxForm: {
            default: '',
        },
        zxFormItem: {
            default: '',
        },
    },

    props: {
        value: [String, Number],
        size: String,
        resize: String,
        form: String,
        disabled: Boolean,
        readonly: Boolean,
        type: {
            type: String,
            default: 'text',
        },
        autosize: {
            type: [Boolean, Object],
            default: false,
        },
        autocomplete: {
            type: String,
            default: 'off',
        },
        /** @Deprecated in next major version */
        autoComplete: {
            type: String,
            validator(val) {
                process.env.NODE_ENV !== 'production' &&
                    console.warn('[ZX Warn][Input]\'auto-complete\' property will be deprecated in next major version. please use \'autocomplete\' instead.');
                return true;
            },
        },
        validateEvent: {
            type: Boolean,
            default: true,
        },
        suffixIcon: String,
        prefixIcon: String,
        label: String,
        clearable: {
            type: Boolean,
            default: false,
        },
        showPassword: {
            type: Boolean,
            default: false,
        },
        showWordLimit: {
            type: Boolean,
            default: false,
        },
        tabindex: String,
    },

    data() {
        return {
            textareaCalcStyle: {},
            hovering: false,
            focused: false,
            isComposing: false,
            passwordVisible: false,
        };
    },

    computed: {
        _zxFormItemSize() {
            return (this.zxFormItem || {}).zxFormItemSize;
        },
        validateState() {
            return this.zxFormItem ? this.zxFormItem.validateState : '';
        },
        needStatusIcon() {
            return this.zxForm ? this.zxForm.statusIcon : false;
        },
        validateIcon() {
            return {
                validating: 'zx-icon-loading',
                success: 'zx-icon-circle-check',
                error: 'zx-icon-circle-close',
            }[this.validateState];
        },
        textareaStyle() {
            return merge({}, this.textareaCalcStyle, { resize: this.resize });
        },
        inputSize() {
            return this.size || this._zxFormItemSize || (this.$ELEMENT || {}).size;
        },
        inputDisabled() {
            return this.disabled || (this.zxForm || {}).disabled;
        },
        nativeInputValue() {
            return this.value === null || this.value === undefined ? '' : String(this.value);
        },
        showClear() {
            return this.clearable &&
                !this.inputDisabled &&
                !this.readonly &&
                this.nativeInputValue &&
                (this.focused || this.hovering);
        },
        showPwdVisible() {
            return this.showPassword &&
                !this.inputDisabled &&
                !this.readonly &&
                (!!this.nativeInputValue || this.focused);
        },
        isWordLimitVisible() {
            return this.showWordLimit &&
                this.$attrs.maxlength &&
                (this.type === 'text' || this.type === 'textarea') &&
                !this.inputDisabled &&
                !this.readonly &&
                !this.showPassword;
        },
        upperLimit() {
            return this.$attrs.maxlength;
        },
        textLength() {
            if (typeof this.value === 'number') {
                return String(this.value).length;
            }

            return (this.value || '').length;
        },
        inputExceed() {
            // show exceed style if length of initial value greater then maxlength
            return this.isWordLimitVisible &&
                (this.textLength > this.upperLimit);
        },
    },

    watch: {
        value(val) {
            this.$nextTick(this.resizeTextarea);
            if (this.validateEvent) {
                this.dispatch('ZxFormItem', 'el.form.change', [val]);
            }
        },
        // native input value is set explicitly
        // do not use v-model / :value in template
        nativeInputValue() {
            this.setNativeInputValue();
        },
        // when change between <input> and <textarea>,
        // update DOM dependent value and styles
        type() {
            this.$nextTick(() => {
                this.setNativeInputValue();
                this.resizeTextarea();
                this.updateIconOffset();
            });
        },
    },

    created() {
        this.$on('inputSelect', this.select);
    },

    mounted() {
        this.setNativeInputValue();
        this.resizeTextarea();
        this.updateIconOffset();
    },

    updated() {
        this.$nextTick(this.updateIconOffset);
    },

    methods: {
        focus() {
            this.getInput().focus();
        },
        blur() {
            this.getInput().blur();
        },
        getMigratingConfig() {
            return {
                props: {
                    icon: 'icon is removed, use suffix-icon / prefix-icon instead.',
                    'on-icon-click': 'on-icon-click is removed.',
                },
                events: {
                    click: 'click is removed.',
                },
            };
        },
        handleBlur(event) {
            this.focused = false;
            this.$emit('blur', event);
            if (this.validateEvent) {
                this.dispatch('ZxFormItem', 'el.form.blur', [this.value]);
            }
        },
        select() {
            this.getInput().select();
        },
        resizeTextarea() {
            if (this.$isServer) return;
            const { autosize, type } = this;
            if (type !== 'textarea') return;
            if (!autosize) {
                this.textareaCalcStyle = {
                    minHeight: calcTextareaHeight(this.$refs.textarea).minHeight,
                };
                return;
            }
            const minRows = autosize.minRows;
            const maxRows = autosize.maxRows;

            this.textareaCalcStyle = calcTextareaHeight(this.$refs.textarea, minRows, maxRows);
        },
        setNativeInputValue() {
            const input = this.getInput();
            if (!input) return;
            if (input.value === this.nativeInputValue) return;
            input.value = this.nativeInputValue;
        },
        handleFocus(event) {
            this.focused = true;
            this.$emit('focus', event);
        },
        handleCompositionStart() {
            this.isComposing = true;
        },
        handleCompositionUpdate(event) {
            const text = event.target.value;
            const lastCharacter = text[text.length - 1] || '';
            this.isComposing = !isKorean(lastCharacter);
        },
        handleCompositionEnd(event) {
            if (this.isComposing) {
                this.isComposing = false;
                this.handleInput(event);
            }
        },
        handleInput(event) {
            // should not emit input during composition
            if (this.isComposing) return;

            // should remove the following line when we don't support IE
            if (event.target.value === this.nativeInputValue) return;

            this.$emit('input', event.target.value);

            // ensure native input value is controlled
            this.$nextTick(this.setNativeInputValue);
        },
        handleChange(event) {
            this.$emit('change', event.target.value);
        },
        calcIconOffset(place) {
            const elList = [].slice.call(this.$el.querySelectorAll(`.zx-input__${place}`) || []);
            if (!elList.length) return;
            let el = null;
            for (let i = 0; i < elList.length; i++) {
                if (elList[i].parentNode === this.$el) {
                    el = elList[i];
                    break;
                }
            }
            if (!el) return;
            const pendantMap = {
                suffix: 'append',
                prefix: 'prepend',
            };

            const pendant = pendantMap[place];
            if (this.$slots[pendant]) {
                el.style.transform = `translateX(${place === 'suffix' ? '-' : ''}${this.$el.querySelector(`.zx-input-group__${pendant}`).offsetWidth}px)`;
            } else {
                el.removeAttribute('style');
            }
        },
        updateIconOffset() {
            this.calcIconOffset('prefix');
            this.calcIconOffset('suffix');
        },
        clear() {
            this.$emit('input', '');
            this.$emit('change', '');
            this.$emit('clear');
        },
        handlePasswordVisible() {
            this.passwordVisible = !this.passwordVisible;
            this.focus();
        },
        getInput() {
            return this.$refs.input || this.$refs.textarea;
        },
        getSuffixVisible() {
            return this.$slots.suffix ||
                this.suffixIcon ||
                this.showClear ||
                this.showPassword ||
                this.isWordLimitVisible ||
                (this.validateState && this.needStatusIcon);
        },
    },
};
</script>
